/*
 * RED5 Open Source Flash Server - http://code.google.com/p/red5/
 * 
 * Copyright 2006-2014 by respective authors (see below). All rights reserved.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.red5.logging;

import java.lang.reflect.Method;

import org.red5.server.adapter.StatefulScopeWrappingAdapter;
import org.red5.server.api.scope.IScope;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.impl.StaticLoggerBinder;

import ch.qos.logback.classic.LoggerContext;
import ch.qos.logback.classic.selector.ContextSelector;

/**
 * LoggerFactory to simplify requests for Logger instances within
 * Red5 applications. This class is expected to be run only once per
 * logger request and is optimized as such.
 * 
 * @author Paul Gregoire (mondain@gmail.com)
 */
public class Red5LoggerFactory {

	private static boolean useLogback;

	@SuppressWarnings({ "unchecked", "rawtypes" })
	public static Logger getLogger(Class<?> clazz) {
		if (useLogback) {
			//determine the red5 app name or servlet context name
			String contextName = null;
			//if the incoming class extends StatefulScopeWrappingAdapter we lookup the context
			//by scope name
			boolean scopeAware = StatefulScopeWrappingAdapter.class.isAssignableFrom(clazz);
			//System.out.printf("Wrapper - %s\n", StatefulScopeWrappingAdapter.class.isAssignableFrom(clazz));
			if (scopeAware) {
				try {
					Class wrapper = null;
					if ((wrapper = clazz.asSubclass(StatefulScopeWrappingAdapter.class)) != null) {
						Method getScope = wrapper.getMethod("getScope", new Class[0]);
						//NPE will occur here if the scope is not yet set on the application adapter
						IScope scope = (IScope) getScope.invoke(null, new Object[0]);
						contextName = scope.getName();
					}
				} catch (Exception cce) {
					//cclog.warn("Exception {}", e);
				}
			} else {
				//route the Launcher entries to the correct context
				String[] parts = Thread.currentThread().getName().split("Launcher:/");
				if (parts.length > 1) {
					contextName = parts[1];
				}

			}

			/* TODO: For a future day, the context or application will be determined
			//get a reference to our caller
			Class caller = Reflection.getCallerClass(2);
			//System.err.printf("Caller class: %s classloader: %s\n", caller, caller.getClassLoader());

			try {
				//check to see if we've been called by a servlet
				Class sub = caller.asSubclass(Servlet.class);
				//System.err.println("Caller is a Servlet");
			
				//Method[] methods = caller.getMethods();
				//for (Method meth : methods) {
				//	System.err.printf("Method: %s\n", meth.getName());
				//}
				
				Method getContext = caller.getMethod("getServletContext", new Class[0]);
				//System.err.printf("got context method - %s\n", getContext);
				ServletContext context = (ServletContext) getContext.invoke(caller, null);
				System.err.printf("invoked context\n");
			
				contextName = context.getServletContextName();
				//System.err.printf("Servlet context name: %s\n", contextName);

				Method getContextName = context.getClass().getMethod("getServletContextName", new Class[0]);
				System.err.printf("got context name\n");
				Object ctxName = getContextName.invoke(null, new Object[0]);				
				
				System.err.printf("Servlet context result: %s\n", ctxName);
				if (ctxName != null && ctxName instanceof String) {
					contextName = ctxName.toString();
				}	
			} catch (Exception ex) {
				//ex.printStackTrace();
			}
			*/
			return getLogger(clazz, contextName);
		} else {
			return LoggerFactory.getLogger(clazz);
		}
	}

	@SuppressWarnings({ "rawtypes" })
	public static Logger getLogger(Class clazz, String contextName) {
		if (useLogback) {
			Logger logger = null;
			try {
				//check for logback
				Class cs = Class.forName("ch.qos.logback.classic.selector.ContextSelector");
				//trigger an exception if the class doesn't actually exist
				cs.getDeclaredMethods();
				// get the class for static binding
				cs = Class.forName("org.slf4j.impl.StaticLoggerBinder");
				// get its declared methods
				Method[] methods = cs.getDeclaredMethods();
				for (Method method : methods) {
					//ensure method exists
					if (method.getName().equals("getContextSelector")) {
						//System.out.println("Logger context selector method found");
						//get the context selector
						StaticLoggerBinder binder = StaticLoggerBinder.getSingleton();
						Method m1 = binder.getClass().getMethod("getContextSelector", (Class[]) null);
						ContextSelector selector = (ContextSelector) m1.invoke(binder, (Object[]) null);
						//get the context for the given context name or default if null
						LoggerContext ctx = null;
						if (contextName != null && contextName.length() > 0) {
							ctx = selector.getLoggerContext(contextName);
						}
						// and if we get here, fall back to the default context
						if (ctx == null) {
							ctx = selector.getLoggerContext();
						}
						//debug
						//StatusPrinter.print(ctx);
						//get the logger from the context or default context
						logger = ((ctx != null) ? ctx.getLogger(clazz) : selector.getDefaultLoggerContext().getLogger(clazz));
						break;
					}
				}
			} catch (Exception e) {
				//no logback, use whatever logger is in-place
				System.err.printf("Exception %s", e.getMessage());
			}
			if (logger == null) {
				//no logback, use whatever logger is in-place
				logger = LoggerFactory.getLogger(clazz);
			}
			return logger;
		} else {
			return LoggerFactory.getLogger(clazz);
		}
	}

	@SuppressWarnings({ "rawtypes" })
	public static Logger getLogger(String name, String contextName) {
		Logger logger = null;
		try {
			//check for logback
			Class cs = Class.forName("ch.qos.logback.classic.selector.ContextSelector");
			//trigger an exception if the class doesn't actually exist
			cs.getDeclaredMethods();
			// get the class for static binding
			cs = Class.forName("org.slf4j.impl.StaticLoggerBinder");
			// get its declared methods
			Method[] methods = cs.getDeclaredMethods();
			for (Method method : methods) {
				//ensure method exists
				if (method.getName().equals("getContextSelector")) {
					//System.out.println("Logger context selector method found");
					//get the context selector
					StaticLoggerBinder binder = StaticLoggerBinder.getSingleton();
					Method m1 = binder.getClass().getMethod("getContextSelector", (Class[]) null);
					ContextSelector selector = (ContextSelector) m1.invoke(binder, (Object[]) null);
					//get the context for the given context name or default if null
					LoggerContext ctx = null;
					if (contextName != null && contextName.length() > 0) {
						ctx = selector.getLoggerContext(contextName);
					}
					// and if we get here, fall back to the default context
					if (ctx == null) {
						ctx = selector.getLoggerContext();
					}
					//debug
					//StatusPrinter.print(ctx);
					//get the logger from the context or default context
					logger = ((ctx != null) ? ctx.getLogger(name) : selector.getDefaultLoggerContext().getLogger(name));
					break;
				}
			}
		} catch (Exception e) {
			//no logback, use whatever logger is in-place
			System.err.printf("Exception %s", e.getMessage());
		}
		if (logger == null) {
			//no logback, use whatever logger is in-place
			logger = LoggerFactory.getLogger(name);
		}
		return logger;
	}

	public static ContextSelector getContextSelector() {
		ContextSelector selector = null;
		StaticLoggerBinder binder = StaticLoggerBinder.getSingleton();
		try {
			Method m1 = binder.getClass().getMethod("getContextSelector", (Class[]) null);
			selector = (ContextSelector) m1.invoke(binder, (Object[]) null);
		} catch (Exception e) {
			System.err.printf("Exception %s", e.getMessage());
		}
		return selector;
	}

	public static void setUseLogback(boolean useLogback) {
		Red5LoggerFactory.useLogback = useLogback;
	}

}